/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.meta.util.database;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.SQLException;

public final class TypeConvertUtils {

    private static final Logger logger = LoggerFactory.getLogger(TypeConvertUtils.class);

    private TypeConvertUtils() {
        throw new IllegalStateException("Utility class can not create instance!");
    }

    public static String castToString(final Object in) {
        if (in instanceof Character) {
            return in.toString();
        } else if (in instanceof String) {
            return in.toString();
        } else if (in instanceof Character) {
            return in.toString();
        } else if (in instanceof java.sql.Clob) {
            return clob2Str((java.sql.Clob) in);
        } else if (in instanceof Number) {
            return in.toString();
        } else if (in instanceof java.sql.RowId) {
            return in.toString();
        } else if (in instanceof Boolean) {
            return in.toString();
        } else if (in instanceof java.util.Date) {
            return in.toString();
        } else if (in instanceof java.time.LocalDate) {
            return in.toString();
        } else if (in instanceof java.time.LocalTime) {
            return in.toString();
        } else if (in instanceof java.time.LocalDateTime) {
            return in.toString();
        } else if (in instanceof java.time.OffsetDateTime) {
            return in.toString();
        } else if (in instanceof java.sql.SQLXML) {
            return in.toString();
        } else if (in instanceof java.sql.Array) {
            return in.toString();
        } else if (in instanceof java.util.UUID) {
            return in.toString();
        } else if ("org.postgresql.util.PGobject".equals(in.getClass().getName())) {
            return in.toString();
        } else if ("org.postgresql.jdbc.PgSQLXML".equals(in.getClass().getName())) {
            try {
                Class<?> clz = in.getClass();
                Method getString = clz.getMethod("getString");
                return getString.invoke(in).toString();
            } catch (Exception e) {
                return "";
            }
        } else if (in.getClass().getName().equals("oracle.sql.INTERVALDS")) {
            return in.toString();
        } else if (in.getClass().getName().equals("oracle.sql.INTERVALYM")) {
            return in.toString();
        } else if (in.getClass().getName().equals("oracle.sql.TIMESTAMPLTZ")) {
            return in.toString();
        } else if (in.getClass().getName().equals("oracle.sql.TIMESTAMPTZ")) {
            return in.toString();
        } else if (in.getClass().getName().equals("oracle.sql.BFILE")) {
            Class<?> clz = in.getClass();
            try {
                Method methodFileExists = clz.getMethod("fileExists");
                boolean exists = (boolean) methodFileExists.invoke(in);
                if (!exists) {
                    return "";
                }

                Method methodOpenFile = clz.getMethod("openFile");
                methodOpenFile.invoke(in);

                try {
                    Method methodCharacterStreamValue = clz.getMethod("getBinaryStream");
                    java.io.InputStream is = (java.io.InputStream) methodCharacterStreamValue.invoke(in);

                    String line;
                    StringBuilder sb = new StringBuilder();

                    java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(is));
                    while ((line = br.readLine()) != null) {
                        sb.append(line);
                    }

                    return sb.toString();
                } finally {
                    Method methodCloseFile = clz.getMethod("closeFile");
                    methodCloseFile.invoke(in);
                }
            } catch (java.lang.reflect.InvocationTargetException ex) {
                logger.warn("Error for handle oracle.sql.BFILE: ", ex);
                return "";
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else if (in.getClass().getName().equals("microsoft.sql.DateTimeOffset")) {
            return in.toString();
        } else if (in instanceof byte[]) {
            return new String((byte[]) in);
        }

        return null;
    }

    public static byte[] castToByteArray(final Object in) {
        if (in instanceof byte[]) {
            return (byte[]) in;
        } else if (in instanceof java.util.Date) {
            return in.toString().getBytes();
        } else if (in instanceof java.sql.Blob) {
            return blob2Bytes((java.sql.Blob) in);
        } else if (in instanceof String || in instanceof Character) {
            return in.toString().getBytes();
        } else if (in instanceof java.sql.Clob) {
            return clob2Str((java.sql.Clob) in).getBytes();
        } else {
            return toByteArray(in);
        }
    }

    public static Object castByDetermine(final Object in) {
        if (null == in) {
            return null;
        }

        if (in instanceof BigInteger) {
            return ((BigInteger) in).longValue();
        } else if (in instanceof BigDecimal) {
            BigDecimal decimal = (BigDecimal) in;
            if (decimal.doubleValue() > 2.147483647E9D || decimal.doubleValue() < -2.147483648E9D) {
                return 0D;
            }
            return decimal.doubleValue();
        } else if (in instanceof java.sql.Clob) {
            return clob2Str((java.sql.Clob) in);
        } else if (in instanceof java.sql.Array
                || in instanceof java.sql.SQLXML) {
            try {
                return castToString(in);
            } catch (Exception e) {
                logger.warn("Unsupported type for convert {} to java.lang.String", in.getClass().getName());
                return null;
            }
        } else if (in instanceof java.sql.Blob) {
            try {
                return blob2Bytes((java.sql.Blob) in);
            } catch (Exception e) {
                logger.warn("Unsupported type for convert {} to byte[] ", in.getClass().getName());
                return null;
            }
        } else if (in instanceof java.sql.Struct) {
            logger.warn("Unsupported type for convert {} to java.lang.String", in.getClass().getName());
            return null;
        }

        return in;
    }

    public static byte[] blob2Bytes(java.sql.Blob blob) {
        try (java.io.InputStream inputStream = blob.getBinaryStream()) {
            try (java.io.BufferedInputStream is = new java.io.BufferedInputStream(inputStream)) {
                byte[] bytes = new byte[(int) blob.length()];
                int len = bytes.length;
                int offset = 0;
                int read = 0;
                while (offset < len && (read = is.read(bytes, offset, len - offset)) >= 0) {
                    offset += read;
                }
                return bytes;
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String clob2Str(java.sql.Clob clob) {
        try (java.io.Reader is = clob.getCharacterStream()) {
            java.io.BufferedReader reader = new java.io.BufferedReader(is);
            String line = reader.readLine();
            StringBuilder sb = new StringBuilder();
            while (line != null) {
                sb.append(line);
                line = reader.readLine();
            }
            return sb.toString();
        } catch (SQLException | java.io.IOException e) {
            logger.warn("Field Value convert from java.sql.Clob to java.lang.String failed:", e);
            return null;
        }
    }

    private static byte[] toByteArray(Object obj) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(obj);
            oos.flush();
            return bos.toByteArray();
        } catch (Exception e) {
            logger.error("Field value convert from {} to byte[] failed:", obj.getClass().getName(), e);
            throw new RuntimeException(e);
        }
    }

}
